package app.server.project.main.service.service;

import app.server.project.core.constants.Def;
import app.server.project.core.constants.TcpCmd;
import app.server.project.core.exception.SysError;
import app.server.project.core.util.AssertUtil;
import app.server.project.core.util.SysTimeUtil;
import app.server.project.core.websocket.WSMgr;
import app.server.project.main.service.bo.event.RechargeSuccessEvent;
import app.server.project.main.service.constants.GlobalCfgKey;
import app.server.project.main.service.constants.SignStatus;
import app.server.project.main.service.db.dao.cfg.GlobalCfgDao;
import app.server.project.main.service.db.dao.user.EnergyInfoDao;
import app.server.project.main.service.db.dao.user.UserSignDao;
import app.server.project.main.service.db.entity.user.UserSignEntity;
import app.server.project.main.service.dto.sign.SignDto;
import app.server.project.main.service.dto.sign.SignResp;
import app.server.project.main.service.service.recharge.CallbackType;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.date.LocalDateTimeUtil;
import cn.hutool.core.util.IdUtil;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Service;

import java.time.Duration;
import java.util.ArrayList;
import java.util.Date;
import java.util.Objects;

@Service
@AllArgsConstructor
@Slf4j
public class LoginEnergyService {

    private final static long MAX_DAY = 3;
    private final static long WithDraw = 30;

    private GlobalCfgDao globalCfgDao;
    private EnergyInfoDao energyInfoDao;
    private EnergyService energyService;
    private UserSignDao userSignDao;


    private void chk(long userId) {
        //检查是否已经领取过
        var energyInfo = this.energyInfoDao.getInfoById(userId);
        if (energyInfo.getDrawTime() > Def.Zero) {
            AssertUtil.isTrue(!DateUtil.isSameDay(new Date(energyInfo.getDrawTime()), SysTimeUtil.getAppDate()), SysError.Repeat_Again_Draw_Energy);
        }
    }


    private void addSignTime(long userId, long days) {
        var dbs = this.userSignDao.getLast30(userId, days);
        dbs.add(Def.Zero, UserSignEntity.of(userId, IdUtil.getSnowflakeNextId()));
        //开始检查是否超标
        if (dbs.size() > days) {
            var temp = dbs.subList((int) days, dbs.size());
            //temp.forEach(v -> v.setIsDeleted(Def.True));
            dbs.removeAll(temp);
        }
    }

    public SignResp getSigns(long userId) {
        //强制签到天数30天
        long signTime = WithDraw;
        var withDraw = WithDraw;

        if (this.globalCfgDao.has(GlobalCfgKey.WithDraw_Login_Day)) {
            withDraw = this.globalCfgDao.getOneByKey(GlobalCfgKey.WithDraw_Login_Day).getLong();
            signTime = this.globalCfgDao.getOneByKey(GlobalCfgKey.WithDraw_Login_Day).getLong();
        }
        var energyInfo = this.energyInfoDao.getInfoById(userId);
        var ret = new ArrayList<SignDto>();
        //第一次打卡
        if (Objects.isNull(energyInfo.getFirstTime())) {
            for (int i = 0; i < signTime; i++) {
                if (i == Def.Zero) {
                    ret.add(SignDto.of(i + 1, SignStatus.NonCheckIn, true));
                } else {
                    ret.add(SignDto.of(i + 1, SignStatus.Future, false));
                }
            }
            return SignResp.of(ret, Def.Zero);
        }
        var now = SysTimeUtil.getAppTimeLocalTime();

        //查询首次打卡记录
        var dayIndex = Def.Zero;
        //首次打卡时间不空
        var diffVal = Duration.between(energyInfo.getFirstTime(), now);
        dayIndex = Math.toIntExact(diffVal.toDays() / signTime);
        var start = dayIndex * signTime + 1;
        var startTime = energyInfo.getFirstTime().plusDays(start - 1);
        var dbs = this.userSignDao.getLast30(userId, signTime);


        var todayVal = LocalDateTimeUtil.beginOfDay(now);
        //生成打卡记录
        for (int i = 0; i < signTime; i++) {
            var startVal = LocalDateTimeUtil.beginOfDay(startTime);
            java.time.LocalDateTime finalStartTime = startTime;
            var flag = dbs.stream().anyMatch(v -> LocalDateTimeUtil.isSameDay(v.getSignTime(), finalStartTime));
            //当天数据
            if (todayVal.isEqual(startVal)) {
                ret.add(SignDto.of((int) start, flag ? SignStatus.CheckIn : SignStatus.NonCheckIn, true));
            } else {
                if (startVal.isBefore(todayVal)) {
                    ret.add(SignDto.of((int) start, flag ? SignStatus.CheckIn : SignStatus.NonCheckIn, false));
                }
                if (startVal.isAfter(todayVal)) {
                    ret.add(SignDto.of((int) start, SignStatus.Future, false));
                }
            }
            start++;
            startTime = startTime.plusDays(1);
        }
        var chargeNum = this.calChargeDay(userId);
        return SignResp.of(ret, chargeNum);
    }


    private long calChargeDay(long userId) {
        var energyInfo = this.energyInfoDao.getInfoById(userId);
        if (energyInfo.getSignFlag() == Def.FALSE) {
            //重新计算打卡次数
            energyInfo.setSignFlag(Def.True);
            energyInfo.setSignNum(this.userSignDao.countByUserId(userId));
        }
        //没有签到过
        if (Objects.isNull(energyInfo.getFirstTime())) return Def.Zero;
        //连续充值天数
        var chargeTime = LocalDateTimeUtil.beginOfDay(energyInfo.getFirstTime());
        var todayVal = LocalDateTimeUtil.beginOfDay(SysTimeUtil.getAppTimeLocalTime().plusDays(1));
        var withDraw = Duration.between(chargeTime, todayVal);
        return withDraw.toDays() >= energyInfo.getSignNum() ? (withDraw.toDays() - energyInfo.getSignNum()) : Def.Zero;
    }

    /**
     * 发送体力
     */
    @EventListener(RechargeSuccessEvent.class)
    public void rewardSign(RechargeSuccessEvent event) {
        if (event.getCallbackType() != CallbackType.Sign) return;
        log.info("用户:{}  充值成功补签:{}", event.getUserId(), event.getParam());
        //开始补充签到
        var energyInfo = this.energyInfoDao.getInfoById(event.getUserId());
        if (Objects.isNull(energyInfo.getFirstTime())) {
            log.info("没有首次打卡:{}", event);
            return;
        }
        if (energyInfo.getSignFlag() == Def.FALSE) {
            //重新计算打卡次数
            energyInfo.setSignFlag(Def.True);
            energyInfo.setSignNum(this.userSignDao.countByUserId(event.getUserId()));
        }

        var firstTime = LocalDateTimeUtil.beginOfDay(energyInfo.getFirstTime());
        var now = LocalDateTimeUtil.beginOfDay(SysTimeUtil.getAppTimeLocalTime());

        var diffDay = Duration.between(firstTime, now);
        now = now.minusDays(1);
        var allSigns = this.userSignDao.getByUserId(event.getUserId());
        for (int i = 0; i < diffDay.toDays(); i++) {
            java.time.LocalDateTime finalNow = now;
            if (allSigns.stream().noneMatch(v -> LocalDateTimeUtil.isSameDay(v.getSignTime(), finalNow))) {
                var sign = UserSignEntity.of(event.getUserId(), IdUtil.getSnowflakeNextId());
                sign.setSignType(Def.True);
                sign.setSignTime(finalNow);
                allSigns.add(Def.Zero, sign);
            }
            now = now.minusDays(1);
        }
        //开始计算总签到次数
        energyInfo.setSignNum(allSigns.size());
        //开始处理最近30次签到缓存
        allSigns.sort((e1, e2) -> e2.getSignTime().compareTo(e1.getSignTime()));

        long withDraw = WithDraw;
        if (this.globalCfgDao.has(GlobalCfgKey.WithDraw_Login_Day)) {
            withDraw = this.globalCfgDao.getOneByKey(GlobalCfgKey.WithDraw_Login_Day).getLong();
        }
        var dbs = this.userSignDao.getLast30(event.getUserId(), withDraw);
        //清理旧缓存
        dbs.clear();
        if (allSigns.size() >= WithDraw) {
            dbs.addAll(allSigns.subList(Def.Zero, (int) withDraw));
        } else {
            dbs.addAll(allSigns);
        }

        dbs.forEach(v -> {
            v.setSynDb(true);
        });
        //处理连续签到
        energyInfo.setBtcDrawNum(allSigns.size());

        WSMgr.push(event.getUserId(), TcpCmd.Sign, event.getParam());
    }


    /**
     * 领取免费体力
     *
     * @param userId
     */
    public void drawEnergy(long userId) {
        this.chk(userId);
        long free = this.globalCfgDao.getOneByKey(GlobalCfgKey.Energy_Free_Login_Num).getLong();
        long start = this.globalCfgDao.getOneByKey(GlobalCfgKey.Energy_Free_Login_Start_Num).getLong();
        var addNum = this.globalCfgDao.getOneByKey(GlobalCfgKey.Energy_Free_Login_Add_Num).getLong();

        long withDraw = WithDraw;
        if (this.globalCfgDao.has(GlobalCfgKey.WithDraw_Login_Day)) {
            withDraw = this.globalCfgDao.getOneByKey(GlobalCfgKey.WithDraw_Login_Day).getLong();
        }


        var energyInfo = this.energyInfoDao.getInfoById(userId);
        //记录首次打卡
        if (Objects.isNull(energyInfo.getFirstTime())) {
            energyInfo.setFirstTime(SysTimeUtil.getAppTimeLocalTime());
        }
        if (energyInfo.getSignFlag() == Def.FALSE) {
            //重新计算打卡次数
            energyInfo.setSignFlag(Def.True);
            energyInfo.setSignNum(this.userSignDao.countByUserId(userId));
        }
        energyInfo.addSignNum();
        //处理提现登录天数
        if (energyInfo.getBtcDrawNum() == Def.Zero) {
            energyInfo.setBtcDrawNum(1);
            energyInfo.setBtcDrawTime(SysTimeUtil.getAppTimeLong());
            this.addSignTime(userId, withDraw);
        } else {
            if (!DateUtil.isSameDay(new Date(energyInfo.getBtcDrawTime()), SysTimeUtil.getAppDate())) {
                this.addSignTime(userId, withDraw);
            }
            var btcVal = (int) DateUtil.betweenDay(new Date(energyInfo.getBtcDrawTime()), SysTimeUtil.getAppDate(), true);
            if (btcVal == 1) {
                //连续天数+1
                energyInfo.addBtcDrawNum();
            } else {
                energyInfo.setBtcDrawNum(1);
            }
            energyInfo.setBtcDrawTime(SysTimeUtil.getAppTimeLong());
        }


        //已经领满,开始重置
        if (energyInfo.getDrawNum() == MAX_DAY) {
            energyInfo.setDrawTime(SysTimeUtil.getAppTimeLong());
            energyInfo.setDrawNum(1);
        } else {
            var diffVal = Def.Zero;
            //领过，检查是否连续
            if (energyInfo.getDrawTime() != Def.Zero) {
                diffVal = (int) DateUtil.betweenDay(new Date(energyInfo.getDrawTime()), SysTimeUtil.getAppDate(), true);
                if (diffVal == 1) {
                    free += (start + (energyInfo.getDrawNum() - 1) * addNum);
                    //连续天数+1
                    energyInfo.addDrawNum();
                } else {
                    //重置天数
                    energyInfo.setDrawNum(1);
                }
            } else {
                energyInfo.setDrawNum(1);
            }
            energyInfo.setDrawTime(SysTimeUtil.getAppTimeLong());
        }
        this.energyService.addEnergy(userId, free);
    }


    /**
     * 获取可以免费领取体力值
     */
    public long getFreeNum(long userId) {
        //检查是否已经领取过
        var energyInfo = this.energyInfoDao.getInfoById(userId);
        if (energyInfo.getDrawTime() > Def.Zero) {
            if (DateUtil.isSameDay(new Date(energyInfo.getDrawTime()), SysTimeUtil.getAppDate())) {
                return Def.Zero;
            }
        }
        long free = this.globalCfgDao.getOneByKey(GlobalCfgKey.Energy_Free_Login_Num).getLong();
        long start = this.globalCfgDao.getOneByKey(GlobalCfgKey.Energy_Free_Login_Start_Num).getLong();
        var addNum = this.globalCfgDao.getOneByKey(GlobalCfgKey.Energy_Free_Login_Add_Num).getLong();
        //检查登录连续性
        var diffVal = Def.Zero;
        if (energyInfo.getDrawTime() != Def.Zero && energyInfo.getDrawNum() != MAX_DAY) {
            diffVal = (int) DateUtil.betweenDay(new Date(energyInfo.getDrawTime()), SysTimeUtil.getAppDate(), true);
            if (diffVal == 1) {
                free += (start + (energyInfo.getDrawNum() - 1) * addNum);
            }
        }
        return free;
    }
}
